// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

package com.tencent.ncnnyolox;

import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.AnimationDrawable;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.air4.chinesetts.dispatcher.OnTtsStateListener;
import com.air4.chinesetts.dispatcher.TtsStateDispatcher;
import com.alibaba.android.arouter.launcher.ARouter;
import com.blankj.utilcode.util.ToastUtils;
import com.elvishew.xlog.XLog;
import com.example.dao.MessageDao;
import com.jeremyliao.liveeventbus.LiveEventBus;
import com.tencent.ncnnyolox.adapter.MsgAdapter;
import com.tencent.ncnnyolox.basemodule.base.BaseActivity;
import com.tencent.ncnnyolox.basemodule.router.RouterPath;
import com.tencent.ncnnyolox.basemodule.utils.CommonLog;
import com.tencent.ncnnyolox.basemodule.utils.DateUtil;
import com.tencent.ncnnyolox.basemodule.utils.LiveEventKey;
import com.tencent.ncnnyolox.basemodule.utils.MMKey;
import com.tencent.ncnnyolox.basemodule.utils.MMkvUtils;
import com.tencent.ncnnyolox.databinding.ActivityMainBinding;
import com.tencent.ncnnyolox.discern.Message;
import com.tencent.ncnnyolox.net.ApiViewModel;
import com.tencent.ncnnyolox.net.Constant;
import com.tencent.ncnnyolox.net.NetListener;
import com.tencent.ncnnyolox.net.OkHttpClient;
import com.tencent.ncnnyolox.net.bean.request.ChatHistoryAddDTO;
import com.tencent.ncnnyolox.pixelcopy.ImageCopyRequest;
import com.tencent.ncnnyolox.utils.Recognition;
import com.yd.basemodule.base.bean.BaseResult;

import androidx.annotation.LongDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;


public class MainActivity extends BaseActivity<ApiViewModel,ActivityMainBinding> implements SurfaceHolder.Callback, View.OnClickListener {
    public static final int REQUEST_CAMERA = 100; //  random
    private static final String TAG = "MainActivity";
    public static final int REQUEST_READ_WRITE_EXTERNAL_STORAGE = 112;  //  random
    private NcnnYolox ncnnyolox = new NcnnYolox();
    private ActivityMainBinding binding;

    private int facing = 0;
    Context mContext = MainActivity.this;
    private Spinner spinnerModel;
    private Spinner spinnerCPUGPU;
    private int current_model = 0;
    private int current_cpugpu = 0;

    private SurfaceView cameraView;
    private ImageCopyRequest imageCopyRequest;

    private List<Message> list = new ArrayList<>();
    private MsgAdapter adapter;

    private float minProb = 0.40f;

    private int minImageFrame = 5;


    public static final String[] EXTERNAL_STORAGE_PERMISSIONS = {Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};
    private ImageView ivVoice;


    private final float IOU_CLASS_DUPLICATED_THRESHOLD = 0.7f;
    private final int[] OUTPUT_SIZE = new int[]{1, 6300, 85};
    private Timer timer;

    int word1 = 0;
    int word2 = 0;
    int timeCount = 0;

    // 这里将flatten的数组重新解析(xywh,obj,classes).
    /**
     * ╔═══════════╤═════╗      Object detected     ╔═════════════════════╤═════╗
     * ║ C++ Layer │ NDK ║------------------------->║ Android / JVM Layer │ SDK ║
     * ╚═══════════╧═════╝                          ╚═════════════════════╧═════╝
     */
    public void callback(String output, String probability, String x, String y, String width, String height) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.e("first-callback", "output:" + output + "-probability:" + probability);
                mBinding.etHome.setText("output:" + output + "-probability:" + probability);
                dealLabel(output, Float.parseFloat(probability), new RectF(Float.parseFloat(x), Float.parseFloat(y), Float.parseFloat(width), Float.parseFloat(height)));
                //TODO 该处输出识别结果
                Log.i("callBack", String.format("label: %s, \nprobability: %s, Rect: [x: %s, y: %s, width: %s, height: %s ]",
                        output, probability, x, y, width, height));
                XLog.d("初始识别结果"+String.format("label: %s, \nprobability: %s, Rect: [x: %s, y: %s, width: %s, height: %s ]",
                        output, probability, x, y, width, height));
                binding.textViewCurrentLabel.setText(String.format("label: %s, \nprobability: %s, Rect: [x: %s, y: %s, width: %s, height: %s ]",
                        output, probability, x, y, width, height));
            }
        });
    }
    ArrayList<Recognition> allRecognitions = new ArrayList<>();
    boolean isCollectWord;

    public void dealLabel(String label, Float prob, RectF rectF) {
//
//
//        Recognition recognition = new Recognition(label, prob, rectF);
//        allRecognitions.add(recognition);
//        if (allRecognitions.size() >= 10) {
//            List<Recognition> first = first(allRecognitions);
//            makeSentence(first);
//            allRecognitions.clear();
//
//        }
//        if (timer != null) {
//            timer.cancel();
//        }
        if (prob > minProb){
            Recognition recognition = new Recognition(label, prob, rectF);
            word1 = word1 + 1;
            allRecognitions.add(recognition);
            processRecognitions(true);
        }
    }

    private void processRecognitions(boolean onlyShowText) {
        if (!allRecognitions.isEmpty()) {
            //List<Recognition> first = first(allRecognitions);
            List<Recognition> first = two(allRecognitions);
            Log.e("first-识别结果---first.size",
                    Arrays.toString(first.stream().map(Recognition::getLabel).collect(Collectors.toList()).toArray()));
            if (first.isEmpty()){
                return;
            }
            makeSentence(first, onlyShowText);
            if (!onlyShowText) {
                allRecognitions.clear();
            }
        }
    }

    @NonNull
    private List<Recognition> two(ArrayList<Recognition> list){
        List<Recognition> result = new ArrayList<>();

        int count = 1;
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i).getLabel().equals(list.get(i - 1).getLabel())) {
                count++;
            } else {
                if (count > minImageFrame) {
                    result.add(list.get(i - 1));
                } else {
//                    for (int j = 0; j < count; j++) {
//                        result.add(list.get(i - 1));
//                    }
                }
                count = 1;
            }
        }
        if (count > minImageFrame) {
            result.add(list.get(list.size() - 1));
        } else {
//            for (int j = 0; j < count; j++) {
//                result.add(list.get(list.size() - 1));
//            }
        }

        List<Recognition> finalResult = new ArrayList<>();
        for (int i = 0; i < result.size(); i++) {
            if (i == 0 || !result.get(i).getLabel().equals(result.get(i - 1).getLabel())) {
                finalResult.add(result.get(i));
            }
        }

        return finalResult;

    }


    /**
     * 第一次去找
     */
    private List<Recognition> first(ArrayList<Recognition> arrayList) {
        List<Recognition> nmsRecognitions = new CopyOnWriteArrayList<>();
        // 这里为每个类别做一个队列, 把labelScore高的排前面
        PriorityQueue<Recognition> pq =
                new PriorityQueue<Recognition>(
                        6300,
                        new Comparator<Recognition>() {
                            @Override
                            public int compare(final Recognition l, final Recognition r) {
                                return Float.compare(r.getProbability(), l.getProbability());
                            }
                        });
        for (Recognition recognition : arrayList) {
            if (recognition.getProbability() > 0.6) {
                pq.add(recognition);
            }
        }
        Log.e("first", "pq.size:" + pq.size());
        while (pq.size() > 0) {
            // 概率最大的先拿出来
            Recognition[] a = new Recognition[pq.size()];
            Recognition[] detections = pq.toArray(a);
            Recognition max = detections[0];
            Log.e("first", "Recognition" + max.toString());
            nmsRecognitions.add(max);
            pq.clear();
            for (int k = 1; k < detections.length; k++) {
                Recognition detection = detections[k];
                if (!max.getLabel().equals(detection.getLabel())) {
                    pq.add(detection);
                }
            }
        }
        Log.e("first-识别结果", nmsRecognitions.size() + "");
        return nmsRecognitions;
    }

    /**
     * 第二次抑制
     */


    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        initCamera();


        reload();

        initVoice();

        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (word1 > 0) {
                    if (word2 != word1) {
                        word2 = word1;
                        timeCount = 0;
                    } else {
                        if (timeCount >= 3) {
                            processRecognitions(false);
                            word1 = 0;
                            word2 = 0;
                            timeCount = 0;
                        }
                        timeCount = timeCount + 1;
                    }
                }
            }
        }, 1000, 1000);

    }

    @Override
    public void onResume() {
        super.onResume();

        if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
        }
        checkCamera();
        initUI();

        //收到常用语
        LiveEventBus.get(LiveEventKey.Common_Word).observe(this, new Observer<Object>() {
            @Override
            public void onChanged(Object o) {
                if (o instanceof String){
                    //消息类型：1常用语，2正常人语音，3键盘输入，4手语识别
                    Message message = new Message((String) o, 1);
                    MyApplication.application.getDaoSession().getMessageDao().insert(message);
                    adapter.addItem(message);
                    mBinding.recycleContent.scrollToPosition(adapter.getItemCount()-1);
                }
            }
        });

        //收到语音播报指令
        LiveEventBus.get(LiveEventKey.To_Speak).observe(this, new Observer<Object>() {
            @Override
            public void onChanged(Object o) {
                if (o instanceof String){

                }

            }
        });

        ncnnyolox.openCamera(facing);


    }
//    private TextToSpeech textToSpeech;
    private void initVoice() {
        TtsStateDispatcher.getInstance().addListener(new OnTtsStateListener() {
            @Override
            public void onTtsReady() {
                CommonLog.INSTANCE.e(TAG,"语音播放准备");
            }

            @Override
            public void onTtsStart(String text) {
                CommonLog.INSTANCE.e(TAG,"语音播放开始");
            }

            @Override
            public void onTtsStop() {
                CommonLog.INSTANCE.e(TAG,"语音播放停止");
            }
        });

    }

    private void initUI() {
        RecyclerView recyclerView = findViewById(R.id.recycle_content);
        // 设置布局管理器，这里使用 LinearLayoutManager
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter();
        recyclerView.setAdapter(adapter);
        Button btnDelete = findViewById(R.id.btn_delete);
        Button btn = findViewById(R.id.btn);
        TextView tv = findViewById(R.id.tv);
        binding.tvChangyong.setOnClickListener(this);
        binding.title.getLeftView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        binding.title.getRightView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build(RouterPath.CommonWord_ChatRecordActivity).navigation();
            }
        });
        btnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyApplication.application.getDaoSession().getMessageDao().deleteAll();
            }
        });
        //摄像头切换
        ImageView ivSwitchCamera = findViewById(R.id.iv_switch_camera);
        ivSwitchCamera.setOnClickListener(this);


        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {


            }
        });
        //设置软键盘
        binding.etHome.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {

                    ChatHistoryAddDTO addDTO=new ChatHistoryAddDTO("", MMkvUtils.INSTANCE.decodeString(MMKey.memberId), binding.etHome.getText().toString(),DateUtil.getDateTime4(new Date()),"1","1");
                    viewModel.addRecord(addDTO).observe(MainActivity.this, new Observer<BaseResult<String>>() {
                        @Override
                        public void onChanged(BaseResult<String> stringBaseResult) {
                            if (stringBaseResult.code()==200){

                                //消息类型：1常用语，2正常人语音，3键盘输入，4手语识别
                                Message message = new Message(binding.etHome.getText().toString(), 3);
                                MyApplication.application.getDaoSession().getMessageDao().insert(message);
                                adapter.addItem(message);
                                recyclerView.scrollToPosition(adapter.getItemCount()-1);
                                binding.etHome.setText("");
                            }
                        }
                    });

                    return true;
                }
                return false;
            }
        });

    }

    private void initCamera() {
        cameraView = (SurfaceView) findViewById(R.id.cameraview);

        cameraView.getHolder().setFormat(PixelFormat.RGBA_8888);
        cameraView.getHolder().addCallback(this);
        ncnnyolox.injectObjectReference(this); /** Get a reference to the Object MainActivity*/

        imageCopyRequest = new ImageCopyRequest(cameraView);

        if (!hasPermissions(mContext, EXTERNAL_STORAGE_PERMISSIONS)) {
            ActivityCompat.requestPermissions((Activity) mContext, EXTERNAL_STORAGE_PERMISSIONS,
                    REQUEST_READ_WRITE_EXTERNAL_STORAGE);

            Log.d(TAG, "Requesting permissions");

        } else {
            Log.d(TAG, "Ok: READ_EXTERNAL_STORAGE permission already granted");
            imageCopyRequest.setHasPermissionToSave(true);
        }

        binding.buttonCropImage.setOnClickListener(arg0 -> {
            imageCopyRequest.start();
        });

        Button buttonSwitchCamera = (Button) findViewById(R.id.buttonSwitchCamera);
        buttonSwitchCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                int new_facing = 1 - facing;
                ncnnyolox.closeCamera();
                ncnnyolox.openCamera(new_facing);
                facing = new_facing;
            }
        });

        spinnerModel = (Spinner) findViewById(R.id.spinnerModel);
        spinnerModel.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
                if (position != current_model) {
                    current_model = position;
                    reload();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
            }
        });

        spinnerCPUGPU = (Spinner) findViewById(R.id.spinnerCPUGPU);
        spinnerCPUGPU.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
                if (position != current_cpugpu) {
                    current_cpugpu = position;
                    reload();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
            }
        });
    }


    private void reload() {
        boolean ret_init = ncnnyolox.loadModel(getAssets(), current_model, current_cpugpu);
        if (!ret_init) {
            ToastUtils.showLong("系统异常,请稍后再试");
            finish();
            Log.e("MainActivity", "ncnnyolox loadModel failed");
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        ncnnyolox.setOutputWindow(holder.getSurface());
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }



    @Override
    public void onPause() {
        super.onPause();

        ncnnyolox.closeCamera();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.iv_switch_camera:
                int new_facing = 1 - facing;
                ncnnyolox.closeCamera();
                ncnnyolox.openCamera(new_facing);
                facing = new_facing;

                break;
            case R.id.tv_changyong:
                ARouter.getInstance().build(RouterPath.CommonWord_Activity).navigation();

                break;


        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_READ_WRITE_EXTERNAL_STORAGE) {
            if (grantResults.length == 0) {
                Log.e(TAG, "grantResults.length == 0");

            }
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                imageCopyRequest.setHasPermissionToSave(true);
                Log.d(TAG, "Great! we now have permissions for storage.");

            } else {
                imageCopyRequest.setHasPermissionToSave(false);
                Log.e(TAG, "The app was not allowed to write  storage.");

            }
        }
    }

    private static boolean hasPermissions(Context context, String... permissions) {
        if (context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判断相机是否支持
     * INFO_SUPPORTED_HARDWARE_LEVEL有4种值：
     * <p>
     * INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY：处于该级别的设备意味着它只支持Camera1的功能，不具备任何Camera2高级特性，即无法使用Camera2;
     * <p>
     * INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED：支持Camera1的基础功能，支持部分Camera2高级特性的级别;
     * <p>
     * INFO_SUPPORTED_HARDWARE_LEVEL_FULL：支持所有Camera2的高级特性;
     * <p>
     * INFO_SUPPORTED_HARDWARE_LEVEL_3：新增更多Camera2高级特性，例如YUV数据的后处理。
     */

    private boolean checkCamera() {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        String[] cameraIdList = new String[0];
        try {
            cameraIdList = manager.getCameraIdList();
        } catch (CameraAccessException e) {
            Log.e("checkCamera", e.getLocalizedMessage());
        }
        if (cameraIdList.length == 0) {
            Log.d("checkCamera", "相机不支持");
            return false;
        } else {
            for (String s : cameraIdList) {
                CameraCharacteristics characteristics = null;
                try {
                    characteristics = manager.getCameraCharacteristics(s);
                } catch (CameraAccessException e) {
                    Log.e("checkCamera", e.getMessage());
                }
                Log.d("checkCamera", "INFO_SUPPORTED_HARDWARE_LEVEL " + characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL));
                if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL).equals(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY)) {
                    Toast.makeText(this, "相机不支持手语识别功能", Toast.LENGTH_LONG).show();
                    Log.d("checkCamera", "相机不支持手语识别功能");
                    return false;
                }
            }
            return true;
        }


    }

    public void makeSentence(List<Recognition> recognitionList, boolean onlyShowText){

        String reason="";

        for (Recognition recognition : recognitionList) {
            reason=reason+" "+recognition.getLabel();
        }
        CommonLog.INSTANCE.d(TAG,reason);
        OkHttpClient okHttpClient = new OkHttpClient();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String reasonTv="";

                for (Recognition recognition : recognitionList) {
                    reasonTv=reasonTv+" "+recognition.getLabel();
                }
                binding.tvBeforeLabel.setText("成句前结果--"+reasonTv);
                XLog.d("成句前结果"+reasonTv);
            }
        });

        if (!onlyShowText) {
            //"手语句子:'"+reason+"',翻译成中文白话文是?只返回成句结果"
            //"请将下面词组组成合适的句子，只要句子，不需要其他解释说明,"+reason
            okHttpClient.makePostRequest(Constant.url, Constant.API_KEY, "请将下面词组组成合适的句子，只要句子，不需要其他解释说明,"+reason, new NetListener() {
                @Override
                public void onSuccess(String response) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            binding.tvSentence.setText("成句后结果--"+response);
                            XLog.d("成句后结果为-----"+response);
                            CommonLog.INSTANCE.d(TAG,"成句结果为"+response);
                            //插入数据库 消息类型：1常用语，2正常人语音，3键盘输入，4手语识别
                            Message message = new Message(response, 4);
                            MyApplication.application.getDaoSession().getMessageDao().insert(message);
                            adapter.addItem(message);
                            mBinding.recycleContent.scrollToPosition(adapter.getItemCount()-1);
                        }
                    });
                }

                @Override
                public void onFail(String error) {

                }
            });
        }

    }


    @Override
    public void initView(@Nullable Bundle savedInstanceState) {

    }

    @Override
    public void initData() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
